# coding: utf-8
from typing import Union, List

from PySide6.QtCore import Qt, QRectF, QSize, Signal, QPropertyAnimation, QEasingCurve, Property, QUrl
from PySide6.QtGui import QPainter, QPixmap, QWheelEvent, QResizeEvent, QImage, QColor, QIcon
from PySide6.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QGraphicsItem, QWidget, \
    QHBoxLayout, QFileDialog, QVBoxLayout
from qfluentwidgets import TransparentToolButton, FluentIcon as FIF, ToolTipFilter, ToolTipPosition, isDarkTheme, \
    InfoBar, IconWidget, MaskDialogBase, FluentIconBase, IndeterminateProgressRing


class PictureToolButton(TransparentToolButton):
    def _postInit(self):
        self.setFixedSize(30, 30)
        self.installEventFilter(ToolTipFilter(self, position=ToolTipPosition.BOTTOM))


class PictureToolWidget(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.horizontalLayout = QHBoxLayout(self)
        self.rotateBtn = PictureToolButton(FIF.ROTATE, self)
        self.enlargeBtn = PictureToolButton(FIF.ZOOM_IN, self)
        self.shrinkBtn = PictureToolButton(FIF.ZOOM_OUT, self)
        self.fullScreenBtn = PictureToolButton(FIF.FULL_SCREEN, self)
        self.saveBtn = PictureToolButton(FIF.SAVE, self)
        self.closeBtn = PictureToolButton(FIF.CLOSE, self)

        self.__initWidgets()

    def __initWidgets(self):
        self.rotateBtn.setToolTip(self.tr("旋转图片"))
        self.enlargeBtn.setToolTip(self.tr("放大图片"))
        self.shrinkBtn.setToolTip(self.tr("缩小图片"))
        self.fullScreenBtn.setToolTip(self.tr("全屏显示"))
        self.saveBtn.setToolTip(self.tr("保存图片"))
        self.closeBtn.setToolTip(self.tr("关闭窗口"))

        self.horizontalLayout.setAlignment(Qt.AlignRight)
        self.horizontalLayout.setSpacing(6)
        self.horizontalLayout.setContentsMargins(5, 5, 5, 5)
        self.horizontalLayout.addStretch(1)
        self.horizontalLayout.addWidget(self.rotateBtn)
        self.horizontalLayout.addWidget(self.enlargeBtn)
        self.horizontalLayout.addWidget(self.shrinkBtn)
        self.horizontalLayout.addWidget(self.fullScreenBtn)
        self.horizontalLayout.addWidget(self.saveBtn)
        self.horizontalLayout.addWidget(self.closeBtn)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setPen(Qt.NoPen)
        c = 0 if isDarkTheme() else 255
        painter.setBrush(QColor(c, c, c, 100))
        painter.drawRoundedRect(self.rect(), 5, 5)
        painter.end()


class PictureBrowserView(QGraphicsView):
    """
    图片查看器
    """
    closeSignal = Signal(bool)
    finished = Signal()
    error = Signal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._rotationAngle = 0.0
        self.zoomInTimes = 0
        self.maxZoomInTimes = 22
        self.displayedImageSize = QSize(0, 0)
        self._url = QUrl()
        self.reply = None
        self.manager = QNetworkAccessManager(self)
        self.iconWidget = IconWidget(self)
        self.currentIndex = 0
        self._pixmaps = []

        self.verticalLayout = QVBoxLayout(self)
        self.toolsBar = PictureToolWidget(self)
        self.graphicsScene = QGraphicsScene(self)
        self.pixmapItem = QGraphicsPixmapItem()
        self.leftBtn = PictureToolButton(FIF.CARE_LEFT_SOLID, self)
        self.rightBtn = PictureToolButton(FIF.CARE_RIGHT_SOLID, self)
        self.progressRing = IndeterminateProgressRing(self)

        self.__animation = QPropertyAnimation(self)
        self.__animation.setTargetObject(self)
        self.__animation.setPropertyName(b'rotation')
        self.__animation.setDuration(100)
        self.__animation.setEasingCurve(QEasingCurve.InOutQuart)

        self.__initWidgets()
        self.__initSignals()

    def __initWidgets(self):
        self.toolsBar.move(0, 0)
        self.toolsBar.resize(self.width(), 50)

        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)  # 隐藏水平滚动条
        self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)  # 隐藏垂直滚动条
        self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)  # 以鼠标所在位置为锚点进行缩放
        self.pixmapItem.setTransformationMode(Qt.TransformationMode.SmoothTransformation)  # 平滑转型
        self.setRenderHints(
            QPainter.RenderHint.NonCosmeticBrushPatterns | QPainter.RenderHint.LosslessImageRendering | QPainter.RenderHint.SmoothPixmapTransform)  # 平滑像素图变换
        self.setContentsMargins(0, 0, 0, 0)
        self.setViewportMargins(0, 0, 0, 0)
        # 设置布局
        self.verticalLayout.setContentsMargins(10, 10, 10, 10)
        self.verticalLayout.setSpacing(0)
        self.verticalLayout.addWidget(self.toolsBar, 0, Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight)
        # 添加图片标签
        self.graphicsScene.addItem(self.pixmapItem)
        self.setScene(self.graphicsScene)
        # 设置控件样式
        self.setStyleSheet(
            'QGraphicsView{background-color: transparent; border: none;}'
        )
        # 调整控件位置
        self.iconWidget.setFixedSize(80, 80)
        self.leftBtn.hide()
        self.rightBtn.hide()
        self.iconWidget.move((self.width() - self.iconWidget.width()) // 2,
                             (self.height() - self.iconWidget.height()) // 2)
        self.leftBtn.move(10, (self.height() - self.leftBtn.height()) // 2)
        self.rightBtn.move(self.width() - self.rightBtn.width() - 10, (self.height() - self.rightBtn.height()) // 2)

        self.iconWidget.hide()
        self.progressRing.setFixedSize(50, 50)
        self.progressRing.move((self.width() - self.progressRing.width()) // 2,
                               (self.height() - self.progressRing.height()) // 2)
        self.progressRing.hide()

    def __initSignals(self):
        self.toolsBar.rotateBtn.clicked.connect(lambda: self.setRotationByAnimation())
        self.toolsBar.enlargeBtn.clicked.connect(lambda: self.enlargePicture())
        self.toolsBar.shrinkBtn.clicked.connect(lambda: self.shrinkPicture())
        self.toolsBar.fullScreenBtn.clicked.connect(self.__windowFullOrNormal)
        self.toolsBar.saveBtn.clicked.connect(self.__saveImage)
        self.toolsBar.closeBtn.clicked.connect(self.closeSignal)

        self.leftBtn.clicked.connect(self.previous)
        self.rightBtn.clicked.connect(self.next)

    def __windowFullOrNormal(self):
        if self.isFullScreen():
            self.setWindowFlags(Qt.WindowType.SubWindow)
            self.toolsBar.fullScreenBtn.setIcon(FIF.FULL_SCREEN)
            self.showNormal()
        else:
            self.setWindowFlags(Qt.WindowType.Window)
            self.toolsBar.fullScreenBtn.setIcon(FIF.BACK_TO_WINDOW)
            self.showFullScreen()

    def __saveImage(self):
        fileName, t = QFileDialog.getSaveFileName(self, self.tr("保存图片"), "", self.tr("图片 (*.png *.jpg *.jpeg)"))
        if fileName:
            self.pixmapItem.pixmap().save(fileName)
            InfoBar.success('', self.tr("图片已保存到") + fileName, self.window())

    def __getScaleRatio(self):
        """
        获取显示的图像和原始图像的缩放比例
        :return:
        """
        pm = self.pixmapItem.pixmap()
        if pm.isNull():
            return 1

        pw = pm.width()
        ph = pm.height()
        rw = min(1, self.width() / pw)
        rh = min(1, self.height() / ph)
        return min(rw, rh)

    def __setDragEnabled(self, isEnabled: bool):
        """
        设置拖拽是否启动
        :param isEnabled: bool
        :return:
        """
        if isEnabled:
            self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
        else:
            self.setDragMode(QGraphicsView.DragMode.NoDrag)

    def __isEnableDrag(self):
        """
        根据图片的尺寸决定是否启动拖拽功能
        :return:
        """
        v = self.verticalScrollBar().maximum() > 0
        h = self.horizontalScrollBar().maximum() > 0
        return v or h

    def getRotation(self) -> float:
        return self.pixmapItem.rotation()

    def setRotation(self, stepSize: Union[float, int] = 90.0):
        """
        顺时针旋转
        :param stepSize: 步长，旋转角度
        :return:
        """
        if self.pixmapItem.pixmap().isNull():
            return
        # self.pixmapItem.setTransformOriginPoint(self.pixmapItem.boundingRect().center())  # 指定图片旋转中心点
        self.pixmapItem.setRotation(stepSize)

    def setRotationByAnimation(self, stepSize: int = 90):
        """
        顺时针旋转动画
        :param stepSize: 步长，旋转角度
        :return:
        """
        if self.__animation.state() == QPropertyAnimation.State.Running:
            return
        self.__animation.setStartValue(self._rotationAngle)
        self._rotationAngle += stepSize
        self.__animation.setEndValue(self._rotationAngle)
        self.__animation.start()

    def resetTransform(self):
        """
        重置变换
        :return:
        """
        self.zoomInTimes = 0
        self.__setDragEnabled(False)
        super().resetTransform()

    def setAdaptation(self):
        """
        缩放以适应
        :return:
        """
        self.setSceneRect(QRectF(self.pixmapItem.pixmap().rect()))
        self.fitInView(self.pixmapItem)
        self.__setDragEnabled(False)
        self.zoomInTimes = 0

    def setOriginalSize(self):
        """
        设置 1:1 大小
        :return:
        """
        self.resetTransform()
        self.setSceneRect(QRectF(self.pixmapItem.pixmap().rect()))
        self.__setDragEnabled(self.__isEnableDrag())
        self.zoomInTimes = self.getZoomInTimes(self.pixmapItem.pixmap().width())

    def enlargePicture(self, anchor=QGraphicsView.ViewportAnchor.AnchorUnderMouse):
        """
        放大图片
        :return:
        """
        if self.zoomInTimes == self.maxZoomInTimes:
            return
        self.setTransformationAnchor(anchor)
        self.zoomInTimes += 1
        self.scale(1.1, 1.1)
        self.__setDragEnabled(self.__isEnableDrag())

        # 还原 anchor
        self.setTransformationAnchor(anchor)

    def shrinkPicture(self, anchor=QGraphicsView.ViewportAnchor.AnchorUnderMouse):
        """
        缩小图片
        :return:
        """
        if self.zoomInTimes == 0 and not self.__isEnableDrag():
            return

        self.setTransformationAnchor(anchor)

        self.zoomInTimes -= 1

        # 原始图像的大小
        pm = self.pixmapItem.pixmap()
        pw = pm.width()
        ph = pm.height()

        # 实际显示的图像宽度
        w = self.displayedImageSize.width() * 1.1 ** self.zoomInTimes
        h = self.displayedImageSize.height() * 1.1 ** self.zoomInTimes

        if pw > self.width() or ph > self.height():
            # 在窗口尺寸小于原始图像时禁止继续缩小图像比窗口还小
            if w <= self.width() and h <= self.height():
                self.fitInView(self.pixmapItem)
            else:
                self.scale(1 / 1.1, 1 / 1.1)
        else:
            # 在窗口尺寸大于图像时不允许缩小的比原始图像小
            if w <= pw:
                self.resetTransform()
            else:
                self.scale(1 / 1.1, 1 / 1.1)

        self.__setDragEnabled(self.__isEnableDrag())

        # 还原 anchor
        self.setTransformationAnchor(anchor)

    def getZoomInTimes(self, width: int, step: int = 100):
        for i in range(0, self.maxZoomInTimes):
            if width - self.displayedImageSize.width() * 1.1 ** i <= step:
                return i
        return self.maxZoomInTimes

    def fitInView(self, item: QGraphicsItem, mode=Qt.AspectRatioMode.KeepAspectRatio):
        """
        缩放场景使其适应窗口大小
        :param item:
        :param mode:
        :return:
        """
        super().fitInView(item, mode)
        self.displayedImageSize = self.__getScaleRatio() * self.pixmapItem.pixmap().size()
        self.zoomInTimes = 0

    def resizeEvent(self, event: QResizeEvent):
        """
        重写 resizeEvent 事件，调整图片大小
        :param event:
        :return:
        """
        if self.zoomInTimes > 0:
            return
        # 调整图片大小
        ratio = self.__getScaleRatio()
        self.displayedImageSize = self.pixmapItem.pixmap().size() * ratio
        if ratio < 1:
            self.fitInView(self.pixmapItem)
        else:
            self.resetTransform()
        self.setSceneRect(QRectF(self.pixmapItem.pixmap().rect()))
        # 调整控件位置
        self.iconWidget.move((self.width() - self.iconWidget.width()) // 2,
                             (self.height() - self.iconWidget.height()) // 2)
        self.leftBtn.move(10, (self.height() - self.leftBtn.height()) // 2)
        self.rightBtn.move(self.width() - self.rightBtn.width() - 10, (self.height() - self.rightBtn.height()) // 2)
        self.progressRing.move((self.width() - self.progressRing.width()) // 2,
                               (self.height() - self.progressRing.height()) // 2)
        super().resizeEvent(event)

    def wheelEvent(self, e: QWheelEvent):
        """
        滚动鼠标滚轮缩放图片
        :param e:
        :return:
        """
        if e.angleDelta().y() > 0:
            self.enlargePicture()
        else:
            self.shrinkPicture()

    def setCloseVisible(self, visible: bool):
        self.toolsBar.closeBtn.setVisible(visible)

    def setPixmap(self, pixmap: Union[str, QPixmap, QImage], *, headers=None):
        """
        设置图片
        :param pixmap:
        :return:
        """
        if not pixmap:
            return
        if isinstance(pixmap, str):
            if pixmap.startswith('http'):
                self.setUrl(pixmap, headers=headers)
                return
            pixmap = QPixmap(pixmap)
        elif isinstance(pixmap, QImage):
            pixmap = QPixmap.fromImage(pixmap)
        # 设置图片, 并设置场景大小
        self.pixmapItem.setPixmap(pixmap)
        # 缩放图片
        ratio = self.__getScaleRatio()
        self.displayedImageSize = pixmap.size() * ratio
        self.pixmapItem.setTransformOriginPoint(self.pixmapItem.boundingRect().center())
        self.resetTransform()  # 重置变换

    def setUrl(self, url: Union[str, QUrl], headers=None):
        self._url = QUrl(url)
        self.progressRing.show()
        request = QNetworkRequest(self._url)
        if headers:
            for key, value in headers.items():
                request.setRawHeader(key.encode(), value.encode())
        self.reply = self.manager.get(request)
        self.reply.finished.connect(self._onFinished)

    def _onFinished(self):
        if self.reply.error() == QNetworkReply.NetworkError.NoError:
            pixmap = QPixmap()
            pixmap.loadFromData(self.reply.readAll())
            self.setPixmap(pixmap)
        else:
            self.setIcon(FIF.PHOTO)
            self.iconWidget.show()
            self.error.emit(self.reply.errorString())
        self.reply.deleteLater()
        self.reply = None
        self.progressRing.hide()
        self.finished.emit()

    def setIcon(self, icon: Union[str, QIcon, FluentIconBase]):
        self.iconWidget.setIcon(icon)

    def setIconSize(self, width: int, height: int):
        self.iconWidget.setFixedSize(width, height)

    def setPixmaps(self, pixmaps: List[Union[str, QPixmap, QImage]], index: int = 0, *, headers=None):
        self._pixmaps = pixmaps
        self.currentIndex = index
        self.setPixmap(pixmaps[index], headers=headers)
        if len(pixmaps) > 1:
            self.leftBtn.show()
            self.rightBtn.show()

    def setCurrentIndex(self, index: int):
        self.currentIndex = index
        self.setPixmap(self._pixmaps[index])

    def previous(self):
        if self.currentIndex > 0:
            self.currentIndex -= 1
            self.setPixmap(self._pixmaps[self.currentIndex])
        else:
            self.currentIndex = len(self._pixmaps) - 1
            self.setPixmap(self._pixmaps[self.currentIndex])

    def next(self):
        if self.currentIndex < len(self._pixmaps) - 1:
            self.currentIndex += 1
            self.setPixmap(self._pixmaps[self.currentIndex])
        else:
            self.currentIndex = 0
            self.setPixmap(self._pixmaps[self.currentIndex])

    rotation = Property(float, getRotation, setRotation)


class PictureDialog(MaskDialogBase):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.vBoxLayout = QVBoxLayout(self.widget)
        self.pictureBrowser = PictureBrowserView(self.widget)

        self._hBoxLayout.setContentsMargins(0, 0, 0, 0)
        self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
        self.vBoxLayout.setSpacing(0)
        self.vBoxLayout.addWidget(self.pictureBrowser)
        self.pictureBrowser.closeSignal.connect(self.close)

        self.widget.setStyleSheet('background-color: transparent; border: none;')
        self.widget.setGraphicsEffect(None)

    def setPixmap(self, pixmap: Union[str, QPixmap, QImage]):
        self.pictureBrowser.setPixmap(pixmap)
